#ifndef __open_vip_message__
#define __open_vip_message__
#pragma once

#include <cstdint>
#include <string>
#include <cstring>
#include <map>
#include <vector>
#include "mem.h"
#include "xspcomm/xutil.h"

namespace ovip {

class Message
{
public:
    uint64_t id       = 0;
    uint64_t src      = 0;
    uint64_t dst      = 0;
    uint64_t type     = 0;
    uint64_t cstage   = 0;
    uint64_t fstage   = 0;
    uint64_t cycle    = 0;
    uint64_t opcode   = 0;
    uint64_t opparam  = 0;
    uint64_t addr     = 0;
    uint64_t data     = 0;
    uint64_t mask     = 0xffffffffffffffff;
    uint64_t bit_len  = 0;
    uint64_t byte_len = 0;
    uint8_t *pdata    = 0;
    uint8_t *pmask    = 0;
    Message(const Message &m) { *this = m; }
    Message(u_int64_t bit_len = 64)
    {
        this->bit_len  = bit_len;
        this->byte_len = bit_len / 8 + (bit_len % 8 ? 1 : 0);
        if (bit_len > 64) {
            this->pdata = new uint8_t[this->byte_len];
            this->pmask = new uint8_t[this->byte_len];
            memset(this->pdata, 0, this->byte_len);
            memset(this->pmask, 0xff, this->byte_len);
        } else {
            this->pdata = (uint8_t *)&this->data;
            this->pmask = (uint8_t *)&this->mask;
        }
    }
    ~Message()
    {
        if (this->pdata && this->bit_len > 64) {
            delete[] this->pdata;
            this->pdata = 0;
            delete[] this->pmask;
            this->pmask = 0;
        }
    }
    Message &operator=(const Message &msg)
    {
        this->id       = msg.id;
        this->src      = msg.src;
        this->dst      = msg.dst;
        this->type     = msg.type;
        this->cstage   = msg.cstage;
        this->fstage   = msg.fstage;
        this->cycle    = msg.cycle;
        this->opcode   = msg.opcode;
        this->opparam  = msg.opparam;
        this->addr     = msg.addr;
        this->data     = msg.data;
        this->bit_len  = msg.bit_len;
        this->byte_len = msg.byte_len;
        this->mask     = msg.mask;

        if (this->bit_len > 64) {
            if (pdata) delete[] this->pdata;
            if (pmask) delete[] this->pmask;
            this->pdata = new uint8_t[this->byte_len];
            this->pmask = new uint8_t[this->byte_len];
            memcpy(this->pdata, msg.pdata, this->byte_len);
            memcpy(this->pmask, msg.pmask, this->byte_len);
        } else {
            this->pdata = (uint8_t *)&this->data;
            this->pmask = (uint8_t *)&this->mask;
        }
        return *this;
    }
    virtual std::string String()
    {
        return xspcomm::sFmt(
            "id=%d, src=%d, dst=%d, type=%d, cstage=%d, fstage=%d, cycle=%d, opcode=%d, opparam=%d, addr=0x%lx, data=0x%lx, bit_len=%d, byte_len=%d",
            this->id, this->src, this->dst, this->type, this->cstage,
            this->fstage, this->cycle, this->opcode, this->opparam, this->addr,
            this->data, this->bit_len, this->byte_len);
    }
    virtual Message *clone() { return new Message(*this); }
};

class MemBusMsg : public Message
{
public:
    enum class BurstType { Inc, Fix, Wra };

public:
    MemBusMsg(const Message &msg) : Message(msg) {}
    MemBusMsg(u_int64_t bit_len) : Message(bit_len) {}
    bool IsRead() { return this->opcode == 0 && opparam == 0; }
    bool IsReadRsp() { return this->opcode == 0 && opparam == 1; }
    bool IsBurstRead() { return this->opcode == 0 && opparam == 2; }

    bool IsWrite() { return this->opcode == 1 && opparam == 0; }
    bool IsWriteRsp() { return this->opcode == 1 && opparam == 1; }
    bool IsBurstWrite() { return this->opcode == 1 && opparam == 2; }

    bool IsBurstWriteRspLast() { return IsFullStage() && this->IsWriteRsp(); }
    bool IsBurstReadRspLast() { return IsFullStage() && this->IsReadRsp(); }

    bool IsBurstReadInc()
    {
        return this->IsBurstRead() && this->type == (uint64_t)BurstType::Inc;
    }
    bool IsBurstReadFix()
    {
        return this->IsBurstRead() && this->type == (uint64_t)BurstType::Fix;
    }
    bool IsBurstReadWra()
    {
        return this->IsBurstRead() && this->type == (uint64_t)BurstType::Wra;
    }
    bool IsBurstWriteInc()
    {
        return this->IsBurstWrite() && this->type == (uint64_t)BurstType::Inc;
    }
    bool IsBurstWriteFix()
    {
        return this->IsBurstWrite() && this->type == (uint64_t)BurstType::Fix;
    }
    bool IsBurstWriteWra()
    {
        return this->IsBurstWrite() && this->type == (uint64_t)BurstType::Wra;
    }

    void ToRead()
    {
        this->opcode  = 0;
        this->opparam = 0;
    }
    void ToReadRsp()
    {
        this->opcode  = 0;
        this->opparam = 1;
    }
    void ToWrite()
    {
        this->opcode  = 1;
        this->opparam = 0;
    }
    void ToWriteRsp()
    {
        this->opcode  = 1;
        this->opparam = 1;
    }
    void ToBurstRead(BurstType type, uint64_t count)
    {
        this->opcode  = 0;
        this->opparam = 2;
        this->type    = (uint64_t)type;
        this->fstage  = count;
    }
    void ToBurstWrite(BurstType type, uint64_t count)
    {
        this->opcode  = 1;
        this->opparam = 2;
        this->type    = (uint64_t)type;
        this->fstage  = count;
    }

    int GetBurstCount() { return this->fstage; }
    void SetBurstIindex(uint64_t index) { this->cstage = index; }
    void SetMask(uint64_t mask) { this->mask = mask; }
    void SetMaskFromBuf(uint8_t *buff, int size)
    {
        Assert(size <= this->byte_len, "SetMaskFromBuf: size not match");
        memcpy(this->pmask, buff, size);
    }
    void SetMaskFromBits(uint8_t *buffer, int bit_size)
    {
        Assert(bit_size <= this->byte_len,
               "SetMaskFromBits: bit_size not match");
        memset(this->pmask, 0xff, this->byte_len);
        for (int i = 0; i < bit_size; i++) {
            auto byte_index = i / 8;
            auto bit_index  = i % 8;
            if (buffer[byte_index] & (1 << bit_index)) {
                this->pmask[i] = 0xff;
            } else {
                this->pmask[i] = 0x00;
            }
        }
    }
    void SetMaskFromBits(uint64_t mask, int bit_size)
    {
        return SetMaskFromBits((uint8_t *)&mask, bit_size);
    }

    MemBusMsg *AsRead()
    {
        auto msg     = new MemBusMsg(*this);
        msg->opcode  = 0;
        msg->opparam = 0;
        return msg;
    }
    MemBusMsg *AsWrite()
    {
        auto msg     = new MemBusMsg(*this);
        msg->opcode  = 1;
        msg->opparam = 0;
        return msg;
    }
    MemBusMsg *AsReadRsp()
    {
        auto msg     = new MemBusMsg(*this);
        msg->opcode  = 0;
        msg->opparam = 1;
        return msg;
    }
    MemBusMsg *AsWriteRsp()
    {
        auto msg     = new MemBusMsg(*this);
        msg->opcode  = 1;
        msg->opparam = 1;
        return msg;
    }
    bool IsFullStage() { return this->fstage == this->cstage + 1; }
    MemBusMsg *clone() override { return new MemBusMsg(*this); }
    std::string String() override
    {
        if (this->IsRead()) {
            return xspcomm::sFmt(
                "[->Read      : %lx] addr=0x%lx, stage=%d/%d, data=0x%lx", this,
                this->addr, this->cstage, this->fstage, this->data);
        }
        if (this->IsWrite()) {
            return xspcomm::sFmt(
                "[->Write     : %lx] addr=0x%lx, stage=%d/%d, data=0x%lx", this,
                this->addr, this->cstage, this->fstage, this->data);
        }
        if (this->IsBurstReadRspLast()) {
            return xspcomm::sFmt(
                "[<-BurstReadR: %lx] addr=0x%lx, stage=%d/%d, data=0x%lx", this,
                this->addr, this->cstage, this->fstage, this->data);
        }
        if (this->IsBurstRead()) {
            return xspcomm::sFmt(
                "[->BurstRead : %lx] addr=0x%lx, stage=%d/%d, data=0x%lx", this,
                this->addr, this->cstage, this->fstage, this->data);
        }
        if (this->IsBurstWrite()) {
            return xspcomm::sFmt(
                "[->BurstWrite: %lx] addr=0x%lx, stage=%d/%d, data=0x%lx", this,
                this->addr, this->cstage, this->fstage, this->data);
        }
        if (this->IsReadRsp()) {
            return xspcomm::sFmt(
                "[<-ReadRsp   : %lx] addr=0x%lx, stage=%d/%d, data=0x%lx", this,
                this->addr, this->cstage, this->fstage, this->data);
        }
        if (this->IsWriteRsp()) {
            return xspcomm::sFmt(
                "[<-WriteRsp  : %lx] addr=0x%lx, stage=%d/%d, data=0x%lx", this,
                this->addr, this->cstage, this->fstage, this->data);
        }
        return xspcomm::sFmt("[Unknown: %lx] opcode=%d, opparam=%d", this,
                             this->opcode, this->opparam);
    }
};

class MessageManager;
class Device
{
public:
    bool debug     = false;
    uint64_t cycle = 0;
    std::string name;
    std::string channel;
    MessageManager *manager;
    Device() {}
    Device(std::string name) : name(name) {}
    virtual void SetManager(MessageManager &manager)
    {
        this->manager = &manager;
    }
    virtual void SetChannel(std::string ch) { this->channel = ch; }
    virtual bool Handle(std::string ch, Message &msg)
    {
        if (ch == this->channel) { return this->Handle(msg); }
        return false;
    };
    virtual bool Handle(Message &msg) { return false; }
    virtual void Step(uint64_t cycle)
    {
        if (cycle == this->cycle) return;
        this->cycle = cycle;
        this->Step();
    }
    virtual void Step(){};
    virtual bool IsSlave() { return false; }
    virtual bool IsMaster() { return false; }
};

class MessageManager
{
    bool debug     = false;
    uint64_t cycle = 0;
    std::map<std::string, std::vector<Device *>> devices_master;
    std::map<std::string, std::vector<Device *>> devices_slave;
    std::map<std::string, std::vector<Message *>> channels_send;
    std::map<std::string, std::vector<Message *>> channels_recv;

public:
    void __addDevice(std::string ch, Device &dev, bool is_master)
    {
        Assert(is_master == dev.IsMaster(), "Device(slave) type not match");
        Assert(is_master != dev.IsSlave(), "Device(master) type not match");

        if (is_master) {
            if (devices_master.find(ch) == devices_master.end()) {
                devices_master[ch] = std::vector<Device *>();
            }
            devices_master[ch].push_back(&dev);
        } else {
            if (devices_slave.find(ch) == devices_slave.end()) {
                devices_slave[ch] = std::vector<Device *>();
            }
            devices_slave[ch].push_back(&dev);
        }
        if (channels_send.find(ch) == channels_send.end()) {
            channels_send[ch] = std::vector<Message *>();
        }
        if (channels_recv.find(ch) == channels_recv.end()) {
            channels_recv[ch] = std::vector<Message *>();
        }
        dev.SetChannel(ch);
        dev.SetManager(*this);
    }

    void AddMasterDevice(std::string ch, Device &dev)
    {
        __addDevice(ch, dev, true);
    }

    void AddSlaveDevice(std::string ch, Device &dev)
    {
        __addDevice(ch, dev, false);
    }

    MessageManager(xspcomm::XClock &clock, bool debug = false) : debug(debug)
    {
        clock.StepRis([this](u_int64_t c, void *args) { this->Step(c); });
    }

    void Step(uint64_t cycle)
    {
        this->cycle = cycle;
        // send messages
        for (auto &e : channels_send) {
            for (auto it = e.second.begin(); it != e.second.end();) {
                bool is_send = false;
                for (auto &dev : this->devices_slave[e.first]) {
                    if (dev->Handle(e.first, **it)) {
                        delete *it;
                        it      = e.second.erase(it);
                        is_send = true;
                        break;
                    };
                }
                if (!is_send) it++;
            }
        }
        // recv messages
        for (auto &e : channels_recv) {
            for (auto it = e.second.begin(); it != e.second.end();) {
                bool is_recv = false;
                for (auto &dev : this->devices_master[e.first]) {
                    if (dev->Handle(e.first, **it)) {
                        delete *it;
                        it      = e.second.erase(it);
                        is_recv = true;
                        break;
                    };
                }
                if (!is_recv) it++;
            }
        }
        // step devices
        for (auto &e : this->devices_master) {
            for (auto &dev : e.second) { dev->Step(cycle); }
        }
        for (auto &e : this->devices_slave) {
            for (auto &dev : e.second) { dev->Step(cycle); }
        }
    }

    bool SendMsg(std::string ch, Message &msg)
    {
        if (channels_send.find(ch) == channels_send.end()) { return false; }
        auto pmsg = msg.clone();
        channels_send[ch].push_back(pmsg);
        DebugC(this->debug, "%ld SendMsg [%s]: %s", this->cycle, ch.c_str(),
               pmsg->String().c_str());
        return true;
    }

    bool RespMsg(std::string ch, Message &msg)
    {
        if (channels_recv.find(ch) == channels_recv.end()) { return false; }
        auto pmsg = msg.clone();
        channels_recv[ch].push_back(pmsg);
        DebugC(this->debug, "%ld RespMsg [%s]: %s", this->cycle, ch.c_str(),
               pmsg->String().c_str());
        return true;
    }
};

} // namespace ovip
#endif
